/***************************************************************************
 *                                                                         *
 * Copyright (c) 2008 Nuvoton Technolog. All rights reserved.              *
 *                                                                         *
 ***************************************************************************/
 
/****************************************************************************
* FILENAME
*   wb_uart.c
*
* VERSION
*   1.0
*
* DESCRIPTION
*   The UART related function of Nuvoton ARM9 MCU
*
* HISTORY
*   2008-06-25  Ver 1.0 draft by Min-Nan Cheng
*
* REMARK
*   None
 **************************************************************************/
#include <string.h>
#include <stdio.h>
#include "wblib.h"

#define vaStart(list, param) list = (INT8*)((INT)&param + sizeof(param))
#define vaArg(list, type) ((type *)(list += sizeof(type)))[-1]

/* Global variables */
BOOL volatile _sys_bIsUARTInitial = FALSE;
BOOL volatile _sys_bIsUseUARTInt = TRUE;
UINT32 _sys_uUARTClockRate = EXTERNAL_CRYSTAL_CLOCK;
UINT32 UART_BA = UART0_BA;

#define sysTxBufReadNextOne()	(((_sys_uUartTxHead+1)==UART_BUFFSIZE)? NULL: _sys_uUartTxHead+1)
#define sysTxBufWriteNextOne()	(((_sys_uUartTxTail+1)==UART_BUFFSIZE)? NULL: _sys_uUartTxTail+1)
#define UART_BUFFSIZE	256
UINT8 _sys_ucUartTxBuf[UART_BUFFSIZE];
UINT32 volatile _sys_uUartTxHead, _sys_uUartTxTail;
PVOID  _sys_pvOldUartVect;

VOID sysUartISR()
{
	UINT32 volatile regIIR, i;

	regIIR = inpb(REG_UART0_IIR) & 0x0f;

	if (regIIR & 0x02)
	{
		if (_sys_uUartTxHead == _sys_uUartTxTail)	// buffer empty
			outpb(REG_UART0_IER, 0x00);
		else
		{
			for (i=0; i<8; i++)
			{
				outpb(REG_UART0_THR, _sys_ucUartTxBuf[_sys_uUartTxHead]);
				_sys_uUartTxHead = sysTxBufReadNextOne();
				if (_sys_uUartTxHead == _sys_uUartTxTail)	// buffer empty
					break;
			}
		}
	}
}

static VOID sysSetBaudRate(UINT32 uBaudRate)
{
   UINT32 _mBaudValue;

   /* First, compute the baudrate divisor. */
   _mBaudValue = (_sys_uUARTClockRate / (uBaudRate * 16));
   if ((_sys_uUARTClockRate % (uBaudRate * 16)) > ((uBaudRate * 16) / 2))
      _mBaudValue++;
   _mBaudValue -= 2;

   /* Set the divisor latch bit. */
   outpb(REG_UART0_LCR, 0x80);

   /* First write the LSB of the baud rate divisor. */
   outpb(REG_UART0_DLL, (UINT8)_mBaudValue);

   /* Now write the MSB of the baud rate divisor. */
   outpb(REG_UART0_DLM, (UINT8)(_mBaudValue >> 8));

   /* Now that the baud rate has been set turn off Divisor Latch
      to reference data in line control */
   outpb(REG_UART0_LCR, inpb(REG_UART0_LCR) & 0x7F);
}


INT32 sysInitializeUART(WB_UART_T *uart)
{
    /* Check the supplied parity */
    if ((uart->uiParity != WB_PARITY_NONE) &&
        (uart->uiParity != WB_PARITY_EVEN) &&
        (uart->uiParity != WB_PARITY_ODD))

        /* The supplied parity is not valid */
        return WB_INVALID_PARITY;

    /* Check the supplied number of data bits */
    else if ((uart->uiDataBits != WB_DATA_BITS_5) &&
             (uart->uiDataBits != WB_DATA_BITS_6) &&
             (uart->uiDataBits != WB_DATA_BITS_7) &&
             (uart->uiDataBits != WB_DATA_BITS_8))

        /* The supplied data bits value is not valid */
        return WB_INVALID_DATA_BITS;

    /* Check the supplied number of stop bits */
    else if ((uart->uiStopBits != WB_STOP_BITS_1) &&
             (uart->uiStopBits != WB_STOP_BITS_2))

        /* The supplied stop bits value is not valid */
        return WB_INVALID_STOP_BITS;

    /* Verify the baud rate is within acceptable range */
    else if (uart->uiBaudrate < 1200)
        /* The baud rate is out of range */
        return WB_INVALID_BAUD;

    /* Initialize UART */
    switch (uart->uart_no)
    {
    	case WB_UART_0:
    		UART_BA = UART0_BA;
    		break;

    	case WB_UART_1:
    		UART_BA = UART1_BA;
    		break;

    	case WB_UART_2:
    		UART_BA = UART2_BA;
    		break;

    	case WB_UART_3:
    		UART_BA = UART3_BA;
    		break;

    	case WB_UART_4:
    		UART_BA = UART4_BA;
    		break;
	}
    /* Reset the TX/RX FIFOs */
    outpb(REG_UART0_FCR, 0x06);

    /* Setup reference clock */
    _sys_uUARTClockRate = uart->uiFreq;

    /* Setup baud rate */
    sysSetBaudRate(uart->uiBaudrate);

    /* Set the modem control register. Set DTR, RTS to output to LOW,
    and set INT output pin to normal operating mode */ 
    //outpb(UART_MCR, (WB_DTR_Low | WB_RTS_Low | WB_MODEM_En)); 

    /* Setup parity, data bits, and stop bits */
    outpb(REG_UART0_LCR,(uart->uiParity | uart->uiDataBits | uart->uiStopBits));

    /* Timeout if more than ??? bits xfer time */
    outpb(REG_UART0_TOR, 0x80+0x20);

    /* Setup Fifo trigger level and enable FIFO */
    outpb(REG_UART0_FCR, uart->uiRxTriggerLevel);

	// hook UART interrupt service routine
	if (uart->uart_no == WB_UART_0)
	{
		_sys_uUartTxHead = _sys_uUartTxTail = NULL;
	    _sys_pvOldUartVect = sysInstallISR(IRQ_LEVEL_1, IRQ_UART0, (PVOID)sysUartISR);
	    sysEnableInterrupt(IRQ_UART0);
	    sysSetLocalInterrupt(ENABLE_IRQ);
	}
    _sys_bIsUARTInitial = TRUE;

    return Successful;
}


VOID _PutChar_f(UINT8 ucCh)
{
	if (_sys_bIsUseUARTInt == TRUE)
	{
		while(sysTxBufWriteNextOne() == _sys_uUartTxHead) ;	// buffer full

		_sys_ucUartTxBuf[_sys_uUartTxTail] = ucCh;
		_sys_uUartTxTail = sysTxBufWriteNextOne();

	    if (ucCh == '\n')
	    {
			while(sysTxBufWriteNextOne() == _sys_uUartTxHead) ;	// buffer full

			_sys_ucUartTxBuf[_sys_uUartTxTail] = '\r';
			_sys_uUartTxTail = sysTxBufWriteNextOne();
	    }
    
	    if (!(inpb(REG_UART0_IER) & 0x02))
	    	outpb(REG_UART0_IER, 0x02);
	}
	else
	{
	    /* Wait until the transmitter buffer is empty */
	   	while (!(inpb(REG_UART0_LSR) & 0x20));

	    /* Transmit the character */
	   	outpb(REG_UART0_THR, ucCh);

	    if (ucCh == '\n')
	   	{
	   		/* Wait until the transmitter buffer is empty */
	        while (!(inpb(REG_UART0_LSR) & 0x20));
	   		outpb(REG_UART0_THR, '\r');
	   	}
	}
}


VOID sysPutString(INT8 *string)
{
    while (*string != '\0')
    {
        _PutChar_f(*string);
        string++;
    }
}


static VOID sysPutRepChar(INT8 c, INT count)
{
    while (count--)
        _PutChar_f(c);
}


static VOID sysPutStringReverse(INT8 *s, INT index)
{
    while ((index--) > 0)
        _PutChar_f(s[index]);
}


static VOID sysPutNumber(INT value, INT radix, INT width, INT8 fill)
{
    INT8    buffer[40];
    INT     bi = 0;
    UINT32  uvalue;
    UINT16  digit;
    UINT16  left = FALSE;
    UINT16  negative = FALSE;

    if (fill == 0)
        fill = ' ';

    if (width < 0)
    {
        width = -width;
        left = TRUE;
    }

    if (width < 0 || width > 80)
        width = 0;

    if (radix < 0)
    {
        radix = -radix;
        if (value < 0)
        {
            negative = TRUE;
            value = -value;
        }
    }

    uvalue = value;

    do
    {
        if (radix != 16)
        {
            digit = uvalue % radix;
            uvalue = uvalue / radix;
        }
        else
        {
            digit = uvalue & 0xf;
            uvalue = uvalue >> 4;
        }
        buffer[bi] = digit + ((digit <= 9) ? '0' : ('A' - 10));
        bi++;

        if (uvalue != 0)
        {
            if ((radix == 10)
                && ((bi == 3) || (bi == 7) || (bi == 11) | (bi == 15)))
            {
                buffer[bi++] = ',';
            }
        }
    }
    while (uvalue != 0);

    if (negative)
    {
        buffer[bi] = '-';
        bi += 1;
    }

    if (width <= bi)
        sysPutStringReverse(buffer, bi);
    else
    {
        width -= bi;
        if (!left)
            sysPutRepChar(fill, width);
        sysPutStringReverse(buffer, bi);
        if (left)
            sysPutRepChar(fill, width);
    }
}


static INT8 *FormatItem(INT8 *f, INT a)
{
    INT8   c;
    INT    fieldwidth = 0;
    INT    leftjust = FALSE;
    INT    radix = 0;
    INT8   fill = ' ';

    if (*f == '0')
        fill = '0';

    while ((c = *f++) != 0)
    {
        if (c >= '0' && c <= '9')
        {
            fieldwidth = (fieldwidth * 10) + (c - '0');
        }
        else
            switch (c)
            {
                case '\000':
                    return (--f);
                case '%':
                    _PutChar_f('%');
                    return (f);
                case '-':
                    leftjust = TRUE;
                    break;
                case 'c':
                    {
                        if (leftjust)
                            _PutChar_f(a & 0x7f);

                        if (fieldwidth > 0)
                            sysPutRepChar(fill, fieldwidth - 1);

                        if (!leftjust)
                            _PutChar_f(a & 0x7f);
                        return (f);
                    }
                case 's':
                    {
                        if (leftjust)
                            sysPutString((PINT8)a);

                        if (fieldwidth > strlen((PINT8)a))
                            sysPutRepChar(fill, fieldwidth - strlen((PINT8)a));

                        if (!leftjust)
                            sysPutString((PINT8)a);
                        return (f);
                    }
                case 'd':
                case 'i':
                    radix = -10;
                    break;
                case 'u':
                    radix = 10;
                    break;
                case 'x':
                    radix = 16;
                    break;
                case 'X':
                    radix = 16;
                    break;
                case 'o':
                    radix = 8;
                    break;
                default:
                    radix = 3;
                    break;      /* unknown switch! */
            }
        if (radix)
            break;
    }

    if (leftjust)
        fieldwidth = -fieldwidth;

    sysPutNumber(a, radix, fieldwidth, fill);

    return (f);
}


VOID sysPrintf(PINT8 pcStr,...)
{
    WB_UART_T uart;
    INT8  *argP;

	_sys_bIsUseUARTInt = TRUE;
    if (!_sys_bIsUARTInitial)
    {
    	uart.uart_no = WB_UART_0;
        uart.uiFreq = EXTERNAL_CRYSTAL_CLOCK;
        uart.uiBaudrate = 115200;
        uart.uiDataBits = WB_DATA_BITS_8;
        uart.uiStopBits = WB_STOP_BITS_1;
        uart.uiParity = WB_PARITY_NONE;
        uart.uiRxTriggerLevel = LEVEL_1_BYTE;
        sysInitializeUART(&uart);
    }
    
    vaStart(argP, pcStr);       /* point at the end of the format string */
    while (*pcStr)
    {                       /* this works because args are all ints */
        if (*pcStr == '%')
            pcStr = FormatItem(pcStr + 1, vaArg(argP, INT));
        else
            _PutChar_f(*pcStr++);
    }
}


VOID sysprintf(PINT8 pcStr,...)
{
    WB_UART_T uart;
    INT8  *argP;

	_sys_bIsUseUARTInt = FALSE;
    if (!_sys_bIsUARTInitial)
    {
    	uart.uart_no = WB_UART_0;
        uart.uiFreq = EXTERNAL_CRYSTAL_CLOCK;
        uart.uiBaudrate = 115200;
        uart.uiDataBits = WB_DATA_BITS_8;
        uart.uiStopBits = WB_STOP_BITS_1;
        uart.uiParity = WB_PARITY_NONE;
        uart.uiRxTriggerLevel = LEVEL_1_BYTE;
        sysInitializeUART(&uart);
    }
    
    vaStart(argP, pcStr);       /* point at the end of the format string */
    while (*pcStr)
    {                       /* this works because args are all ints */
        if (*pcStr == '%')
            pcStr = FormatItem(pcStr + 1, vaArg(argP, INT));
        else
            _PutChar_f(*pcStr++);
    }
}


INT8 sysGetChar()
{
	while (1)
	{
		if (inpb(REG_UART0_LSR) & 0x01)
			return (inpb(REG_UART0_RBR));
	}
}

VOID sysPutChar(UINT8 ucCh)
{
    /* Wait until the transmitter buffer is empty */
   	while (!(inpb(REG_UART0_LSR) & 0x20));

    /* Transmit the character */
   	outpb(REG_UART0_THR, ucCh);
}

